package com.magenta.maxoptra.commons.db;

import javax.sql.DataSource;
import java.sql.*;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: andreytr
 * Date: 18.02.11
 * Time: 14:39
 */
public class DataSourceQueryExecuter implements QueryExecuter {

    private DataSource dataSource;

    public DataSourceQueryExecuter(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public boolean resultExists(String statement) throws SQLException {
        Connection conn = getConnection();
        try{
            Statement stat = conn.createStatement();
            try{
                ResultSet resultSet = stat.executeQuery(statement);
                try{
                    return resultSet.next();
                }
                finally {
                    resultSet.close();
                }
            }
            finally {
                stat.close();
            }
        }
        finally {
            conn.close();
        }
    }

    @Override
    public int executeUpdate(String sql) throws SQLException {
        return executeUpdate(sql, Collections.<Integer, Object>emptyMap());
    }

    @Override
    public int executeUpdate(String sql, Map<Integer, Object> params) throws SQLException {
        Connection conn = getConnection();
        try{
            PreparedStatement stat = conn.prepareStatement(sql);
            try{
                for(Integer index:params.keySet()) {
                    stat.setObject(index, params.get(index));
                }
                stat.execute();
                return stat.getUpdateCount();
            }
            finally {
                stat.close();
            }
        }
        finally {
            conn.close();
        }
    }

    @Override
    public List getResultList(String sql) throws SQLException {
        return getResultList(sql, new HashMap<Integer, Object>());
    }

    @Override
    public List getResultList(String sql, Map<Integer, Object> params) throws SQLException {
        return getResultList(sql, params, 0);
    }

    @Override
    public List getResultList(String sql, Map<Integer, Object> params, final int maxResults) throws SQLException {
        final List<Object[]> result = new ArrayList<Object[]>();
        iterateResultSet(sql, new ResultSetCommand() {
            @Override
            public void execute(ResultSet resultSet) throws SQLException {
                int cols = resultSet.getMetaData().getColumnCount();
                Object[] object = new Object[cols];
                for (int i = 0; i < cols; i++) {
                    object[i] = resultSet.getObject(i + 1);
                }
                result.add(object);
            }

            @Override
            public int getMaxResults() {
                return maxResults;
            }

			@Override
			public int getFetchSize() {
				return 0;
			}
        }, params);
        return result;
    }

    @Override
    public int iterateResultSet(String sql, ResultSetCommand resultSetCommand) throws SQLException {
        return iterateResultSet(sql, resultSetCommand, new HashMap());
    }

	private static final int FETCH_SIZE = 100;

    @Override
    public int iterateResultSet(String sql, ResultSetCommand resultSetCommand, Map<Integer, Object> params) throws SQLException {
        Connection conn = getConnection();
        try{
            PreparedStatement stat = conn.prepareStatement(sql);
            try{
                for(Integer index:params.keySet()) {
                    stat.setObject(index, params.get(index));
                }
                int maxResults = resultSetCommand.getMaxResults();
                int fetchSize = getSafetyFetchSize(resultSetCommand);
                if (maxResults > 0) {
                    stat.setMaxRows(maxResults);
                    stat.setFetchSize(Math.min(maxResults, fetchSize));
                } else {
                    stat.setFetchSize(fetchSize);
                }
                ResultSet resultSet = stat.executeQuery();
                //resultSet.setFetchSize(50); NOT SUPPORTED BY MySQL Driver
                try {
                    int i = 0;
                    while (resultSet.next()) {
                        resultSetCommand.execute(resultSet);
                        if ( ++i % fetchSize == 0) {//SWITCH TO ANOTHER THREAD IF NEED
                        	Thread.yield();
                        }
                        if ( Thread.interrupted() ) {
                        	return -1;//THREAD INTERRUPTED - BREAK EXECUTION
                        }
                    }
                    return i;
                } finally {
                    resultSet.close();
                }
            }
            finally {
                stat.close();
            }
        }
        finally {
            conn.close();
        }
    }

    private int getSafetyFetchSize(ResultSetCommand resultSetCommand) {
    	int fetchSize = resultSetCommand.getFetchSize();
    	if ( fetchSize > 0 ) {
    		return fetchSize;
    	}
    	return FETCH_SIZE;
	}

	@Override
    public Map<String, String> getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException {
        Connection conn = getConnection();
        try{
            Map<String, String> result = new HashMap<String, String>();
            DatabaseMetaData metaData = conn.getMetaData();
            ResultSet resultSet = metaData.getColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern);
            while(resultSet.next()) {
                result.put(resultSet.getString(4), resultSet.getString(6));
            }
            return result;
        }
        finally {
            conn.close();
        }
    }

    private Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

	@Override
	public int executeUpdate(String sql, Object[] params) throws SQLException {
        Connection conn = getConnection();
        try{
            PreparedStatement stat = conn.prepareStatement(sql);
            try{
            	int index = 1;
                for(Object param : params) {
                    if (param instanceof byte[]) {
                        stat.setBytes(index++, (byte[])param);
                    } else {
                        stat.setObject(index++, param);
                    }
                }
                stat.execute();
                return stat.getUpdateCount();
            }
            finally {
                stat.close();
            }
        }
        finally {
            conn.close();
        }
	}

	@Override
	public boolean resultExists(String statement, Object[] params)
			throws SQLException {
        Connection conn = getConnection();
        try{
            PreparedStatement stat = conn.prepareStatement(statement);
            try{
            	int index = 1;
                for(Object param : params) {
                    stat.setObject(index++, param);
                }
                ResultSet resultSet = stat.executeQuery();
                try{
                    return resultSet.next();
                }
                finally {
                    resultSet.close();
                }
            }
            finally {
                stat.close();
            }
        }
        finally {
            conn.close();
        }
	}

}
